/**
 * License would add here
 */

#ifndef WIN32
// #include <bits/alltypes.h>
#include <sys/time.h>
#else
#include <chrono>
#include <ctime>
#include <iomanip>
#endif

#include <cstring>
#include <ctime>
#include <iomanip>
#include <unistd.h>

#include "log.h"

#define NAPI_UTILS_LOG_TAG "NAPI_UTILS"

#define LOG_STYLE_DEFAULT "\x1b[0m"
#define LOG_STYLE_DEBUG "\x1b[02;30m"
#define LOG_STYLE_INFO "\x1b[00;32m"
#define LOG_STYLE_WARN "\x1b[00;33m"
#define LOG_STYLE_ERROR "\x1b[00;31m"
#define LOG_STYLE_FATAL "\x1b[00;31m"

bool IsStdOut()
{
    return isatty(fileno(stdout));
}

Logger::Logger(LogLevel level)
    : level_(level)
    , domain_(0)
    , tag_(NAPI_UTILS_LOG_TAG)
{ }

Logger::Logger(LogLevel level, const char* tag)
    : level_(level)
    , domain_(0)
    , tag_(tag)
{ }

Logger::Logger(LogLevel level, unsigned int domain, const char* tag)
    : level_(level)
    , domain_(0)
    , tag_(tag)
{ }

Logger::~Logger()
{
#ifndef USE_HILOG
    LoggerPrinter();
#else
    OH_LOG_Print(LOG_APP, level_, domain_, tag_.c_str(), "%{public}s", stream_.str().c_str());
#endif
}

void Logger::LoggerPrinter()
{
#define OUT_STREAM(level)\
    (level <= LogLevel::LOG_INFO ? std::cout : std::cerr)

#define FILL_BLANK(length) \
    std::setw((length)) << std::setfill(' ')

#define FILL_BLANK_LEFT(length, message)                                               \
    "";                                                                                \
    {                                                                                  \
        std::ostringstream stream;                                                     \
        stream << FILL_BLANK((length)) << std::setiosflags(std::ios::left) << message; \
        stream_ << stream.str();                                                       \
    }                                                                                  \
    stream_

#define SWITCH_LOG_TAG(stream, name, level)                                            \
    case (LogLevel::LOG_##level):                                                      \
        if (IsStdOut()) {                                                              \
            stream << LOG_STYLE_##level << #name << LOG_STYLE_DEFAULT " ";             \
        } else {                                                                       \
            stream << #name " ";                                                       \
        }                                                                              \
        break

    constexpr size_t availableLen = 15;
    char formatedTag[availableLen + 1] { 0 };
    size_t length = tag_.size();

    constexpr size_t hiddenLen = 3;
    constexpr size_t headLen = (availableLen - hiddenLen) / 2;
    constexpr size_t tailLen = availableLen - hiddenLen - headLen;

    if (length > availableLen) {
        memcpy(formatedTag, tag_.c_str(), headLen);
        memcpy(formatedTag + headLen + hiddenLen, tag_.c_str() + (length - tailLen), tailLen);

        for (size_t i = headLen; i < headLen + hiddenLen; i++) {
            formatedTag[i] = '.';
        }
    } else {
        memcpy(formatedTag, tag_.c_str(), length);
    }

    OUT_STREAM(level_) << GetTime() << " " << FILL_BLANK(availableLen) << formatedTag << "/";

    switch (level_) {
        SWITCH_LOG_TAG(OUT_STREAM(level_),D, DEBUG);
        SWITCH_LOG_TAG(OUT_STREAM(level_),I, INFO);
        SWITCH_LOG_TAG(OUT_STREAM(level_),W, WARN);
        SWITCH_LOG_TAG(OUT_STREAM(level_),E, ERROR);
        SWITCH_LOG_TAG(OUT_STREAM(level_),F, FATAL);
    default:
        std::abort();
        break;
    }

#undef SWITCH_LOG_TAG

    OUT_STREAM(level_) << stream_.str() << std::endl;
    if (level_ == LogLevel::LOG_FATAL && ABORT_WITH_FATAL) {
        std::abort();
    }
#undef OUT_STREAM
}

std::string Logger::GetTime()
{
    std::ostringstream stream;
#ifndef WIN32
    struct timeval t;
    gettimeofday(&t, nullptr);
    time_t now = std::time(0);

    tm* ltm = localtime(&now);
    size_t constexpr UNIT_MS = 1000;

#define FILL_ZERO(length) \
    std::setw((length)) << std::setfill('0')

    auto timeInUs = t.tv_usec / UNIT_MS;
    stream << (1900 + ltm->tm_year) << "/"
           << FILL_ZERO(2) << (1 + ltm->tm_mon) << "/"
           << FILL_ZERO(2) << (ltm->tm_mday) << " "
           << FILL_ZERO(2) << ltm->tm_hour << ":"
           << FILL_ZERO(2) << ltm->tm_min << ":"
           << FILL_ZERO(2) << ltm->tm_sec << "."
           << FILL_ZERO(3) << (timeInUs % 1000);

#undef FILL_ZERO
#else
    auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
    std::tm* localTime = std::localtime(&now);

    stream << std::put_time(localTime, "%Y/%m/%d %H:%M:%S");
#endif
    return stream.str();
}

#undef LOG_TAG
#undef LOG_STYLE_DEFAULT
#undef LOG_STYLE_DEBUG
#undef LOG_STYLE_INFO
#undef LOG_STYLE_WARN
#undef LOG_STYLE_ERROR
#undef LOG_STYLE_FATAL